/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.file;

import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.core.Converter;
import cn.seaboot.commons.lang.P;
import cn.seaboot.commons.lang.Symbol;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

/**
 * CSV文件导出
 *
 * 此工具，对简单的 csv 文件有效，要求文件不包含特殊字符
 * <p>
 * 算法：先计算得出一行的数据，得出结果之后，再写入到文件中，
 * 一行数据要嘛全部成功，要嘛全部失败，不会出现写入一半的情况。
 * <p>
 * 这个做法对性能来说，是有一定丢失的，
 * 想要追求极致性能，可以从字节写入方面进行优化。
 * 但是对后期维护不一定好。
 *
 * @author Mr.css
 * @version 2020-06-17 11:24
 */
public class CsvUtils {

    private CsvUtils() {
    }

    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @param keys 确定map的取值顺序
     * @throws IOException -
     */
    public static void write(Writer os, List<Map<String, Object>> data, String[] keys) throws IOException {
        for (Map<String, Object> map : data) {
            os.write(mapToLine(map, keys));
        }
    }

    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @param keys 确定map的取值顺序
     * @throws IOException -
     */
    public static void write(Writer os, List<Map<String, Object>> data, String[] keys, Charset charset) throws IOException {
        for (Map<String, Object> map : data) {
            os.write(mapToLine(map, keys, charset));
        }
    }

    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @param keys 确定map的取值顺序
     * @throws IOException -
     */
    public static void writeListMap(OutputStream os, List<Map<String, Object>> data, String[] keys) throws IOException {
        for (Map<String, Object> map : data) {
            os.write(mapToLine(map, keys).getBytes());
        }
    }

    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @param keys 确定map的取值顺序
     * @throws IOException -
     */
    public static void writeListMap(OutputStream os, List<Map<String, Object>> data, String[] keys, Charset charset) throws IOException {
        for (Map<String, Object> map : data) {
            os.write(mapToLine(map, keys, charset).getBytes());
        }
    }


    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @throws IOException -
     */
    public static <T> void writeListArray(Writer os, List<T[]> data) throws IOException {
        for (T[] array : data) {
            os.write(arrayToLine(array));
        }
    }

    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @throws IOException -
     */
    public static <T> void writeListArray(Writer os, List<T[]> data, Charset charset) throws IOException {
        for (T[] array : data) {
            os.write(arrayToLine(array, charset));
        }
    }

    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @throws IOException -
     */
    public static <T> void write(OutputStream os, List<T[]> data) throws IOException {
        for (T[] array : data) {
            os.write(arrayToLine(array).getBytes());
        }
    }

    /**
     * 写出 csv文件
     *
     * @param os   输出
     * @param data data
     * @throws IOException -
     */
    public static <T> void write(OutputStream os, List<T[]> data, Charset charset) throws IOException {
        for (T[] array : data) {
            os.write(arrayToLine(array, charset).getBytes());
        }
    }

    /**
     * 转换为行级数据
     *
     * @param array array
     * @return string
     */
    private static <T> String arrayToLine(@NotNull T[] array) {
        StringBuilder sb = new StringBuilder();
        for (T str : array) {
            sb.append(Converter.toString(str)).append(Symbol.COMMA);
        }
        sb.setLength(sb.length() - 1);
        sb.append(P.lineSeparator());
        return sb.toString();
    }

    /**
     * 转换为行级数据
     * <p>
     * 这个函数只要是解决乱码问题，需要针对系统选择正确的编码
     *
     * @param array   array
     * @param charset 编码
     * @return string
     */
    private static <T> String arrayToLine(@NotNull T[] array, @NotNull Charset charset) {
        StringBuilder sb = new StringBuilder();
        for (T str : array) {
            String v = Converter.toString(str);
            sb.append(new String(v.getBytes(), charset));
            sb.append(Symbol.COMMA);
        }
        sb.setLength(sb.length() - 1);
        sb.append(P.lineSeparator());
        return sb.toString();
    }

    /**
     * 转换为行级数据
     *
     * @param map  map
     * @param keys key 用于确定取值顺序
     * @return string
     */
    private static String mapToLine(@NotNull Map<String, Object> map, @NotNull String[] keys) {
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object v = map.get(key);
            if (v != null) {
                sb.append(Converter.toString(v));
            }
            sb.append(Symbol.COMMA);
        }
        sb.setLength(sb.length() - 1);
        sb.append(P.lineSeparator());
        return sb.toString();
    }

    /**
     * 转换为行级数据
     * <p>
     * 这个函数只要是解决乱码问题，需要针对系统选择正确的编码
     *
     * @param map     map
     * @param keys    key 用于确定取值顺序
     * @param charset 编码
     * @return string
     */
    private static String mapToLine(@NotNull Map<String, Object> map, @NotNull String[] keys, @NotNull Charset charset) {
        StringBuilder sb = new StringBuilder();
        if(CommonUtils.isNotEmpty(keys)){
            for (String key : keys) {
                Object str = map.get(key);
                if (str != null) {
                    String v = Converter.toString(str);
                    sb.append(new String(v.getBytes(), charset));
                }
                sb.append(Symbol.COMMA);
            }
            sb.setLength(sb.length() - 1);
            sb.append(P.lineSeparator());
        }
        return sb.toString();
    }
}
